package org.example.utils;

import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.example.exception.HttpStatusException;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpUtil {

    private static OkHttpClient client;

    private static final String DEFAULT_MEDIA_TYPE = "application/json; charset=utf-8";

    private static final int CONNECT_TIMEOUT = 5;

    private static final int READ_TIMEOUT = 7;

    private static final String GET = "GET";

    private static final String POST = "POST";


    /**
     * 单例模式  获取类实例
     *
     * @author: yh
     * @date: 2022/12/1
     */
    private static OkHttpClient getInstance() {
        if (client == null) {
            synchronized (OkHttpClient.class) {
                if (client == null) {
                    client = new OkHttpClient.Builder()
                            // 设置默认连接超时时间。值为0表示没有超时，否则值必须在1和Integer之间
                            .connectTimeout(60, TimeUnit.SECONDS)
                            // 设置新连接的默认写超时时间。值为0表示没有超时，否则值必须在1和Integer之间
                            .writeTimeout(30, TimeUnit.SECONDS)
                            // 设置新连接的默认读超时时间。值为0表示没有超时，否则值必须在1和Integer之间
                            .readTimeout(30, TimeUnit.SECONDS)
                            .build();
                }
            }
        }
        return client;
    }

    /**
     * 构造 Url
     *
     * @param url        请求路径
     * @param queryParam 参数
     * @return HttpUrl
     */
    public static HttpUrl buildUrl(String url, Map<String, String> queryParam) {
        HttpUrl.Builder builder = HttpUrl.parse(url).newBuilder();
        // addPathSegment：添加路径；
        // addQueryParameter：添加请求参数，允许重复；
        // setQueryParameter：设定请求参数，覆盖重复；
        // username、password：添加用户名、密码；
        queryParam.forEach(builder::addQueryParameter);
        return builder.build();
    }

    /**
     * 普通 GET 请求
     *
     * @param url 请求路径
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doGet(String url) {
        try {
            long startTime = System.currentTimeMillis();
//            addRequestLog(GET, url, null, null);
            Request request = new Request.Builder().url(url).build();
            // 创建一个请求
            Response response = getInstance().newCall(request).execute();
            int httpCode = response.code();
            String result;
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
//                addResponseLog(httpCode, result, startTime);
            } else {
                throw new RuntimeException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);
        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * 携带 HEADER 的 GET 请求
     *
     * @param url     请求路径
     * @param headers 请求头
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doGet(String url, Headers headers) {
        try {
            long startTime = System.currentTimeMillis();
//            addRequestLog(GET, url, null, null);
            Request request = new Request.Builder().url(url).headers(headers).build();
            // 创建一个请求
            Response response = getInstance().newCall(request).execute();
            int httpCode = response.code();
            String result;
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
//                addResponseLog(httpCode, result, startTime);
            } else {
                throw new RuntimeException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);
        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * POST 普通请求
     *
     * @param url       请求路径
     * @param postBody  参数
     * @param mediaType mediaType 默认：json
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doPost(String url, String postBody, String mediaType){
        try {
            long startTime = System.currentTimeMillis();
//            addRequestLog(POST, url, postBody, null);

            MediaType createMediaType = MediaType.parse(mediaType == null ? DEFAULT_MEDIA_TYPE : mediaType);
            Request request = new Request.Builder()
                    .url(url)
                    .post(RequestBody.create(createMediaType, postBody))
                    .build();

            Response response = getInstance().newCall(request).execute();
            int httpCode = response.code();
            String result;
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
//                addResponseLog(httpCode, result, startTime);
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);
        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * POST 表单提交
     *
     * @param url          请求路径
     * @param parameterMap 请求参数
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doPost(String url, Map<String, String> parameterMap) {
        try {
            long startTime = System.currentTimeMillis();
//            List<String> parameterList = new ArrayList<>();
            FormBody.Builder builder = new FormBody.Builder();
            if (parameterMap.size() > 0) {
                for (Map.Entry<String, String> entry : parameterMap.entrySet()) {
                    String parameterName = entry.getKey();
                    String value = entry.getValue();
                    builder.add(parameterName, value);
//                    parameterList.add(parameterName + ":" + value);
                }
            }
//            addRequestLog(POST, url, null, Arrays.toString(parameterList.toArray()));

            FormBody formBody = builder.build();
            Request request = new Request.Builder()
                    .url(url)
                    .post(formBody)
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
                addResponseLog(httpCode, result, startTime);
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * POST 上传单个文件
     *
     * @param url  请求路径
     * @param file 文件
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String uploadFile(String url, File file) {
        try {
            //设置类型为八位字节流
            MediaType mediaType = MediaType.parse("application/octet-stream");
            RequestBody reqBody = RequestBody.create(file, mediaType);

            MultipartBody multiBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    // 设置参数
                    .addFormDataPart("id", "0")
                    .addFormDataPart("file", file.getName(), reqBody)
                    .build();

            Request request = new Request.Builder()
                    .url(url)
                    .post(multiBody)
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * POST 上传多个文件
     *
     * @param url   请求路径
     * @param files 文件集合
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String uploadMultiFile(String url, List<File> files) {
        try {
            //设置类型为八位字节流
            MediaType mediaType = MediaType.parse("application/octet-stream");

            MultipartBody.Builder multiBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM);
            for (File file : files) {
                RequestBody fileBody = RequestBody.create(file, mediaType);
                multiBody.addFormDataPart("file", file.getName(), fileBody);
            }

            Request request = new Request.Builder()
                    .url(url)
                    .post(multiBody.build())
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * PUT 提交
     *
     * @param url       请求路径
     * @param jsonParam 请求参数
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doPut(String url, String jsonParam) {
        try {
            MediaType mediaType = MediaType.parse("application/json;charset=UTF-8");
            RequestBody reqBody = RequestBody.create(jsonParam, mediaType);

            Request request = new Request.Builder()
                    .url(url)
                    .put(reqBody)
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * PUT 提交
     *
     * @param url      请求路径
     * @param paramMap 请求参数 Map类型
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doPut(String url, Map<String, String> paramMap) {
        try {
            HttpUrl httpUrl = buildUrl(url, paramMap);
            RequestBody reqBody = RequestBody.create(new byte[]{}, null);

            Request request = new Request.Builder()
                    .url(httpUrl)
                    .put(reqBody)
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }

    /**
     * DELETE 请求
     *
     * @param url      请求路径
     * @param paramMap 请求参数 Map类型
     * @return response
     * @author: yh
     * @date: 2022/12/1
     */
    public static String doDelete(String url, Map<String, String> paramMap) {
        try {
            HttpUrl httpUrl = buildUrl(url, paramMap);

            Request request = new Request.Builder()
                    .url(httpUrl)
                    .delete()
                    .build();

            Response response = getInstance().newCall(request).execute();
            String result;
            int httpCode = response.code();
            ResponseBody body = response.body();
            if (body != null) {
                result = body.string();
            } else {
                throw new IOException("exception in OkHttpUtil,response body is null");
            }
            return handleHttpResponse(httpCode, result);

        } catch (Exception ex) {
            handleHttpThrowable(ex, url);
            return StringUtils.EMPTY;
        }
    }


    /**
     * 请求前记录日志
     *
     * @param method
     * @param url
     * @param body
     * @param formParam
     * @author: yh
     * @date: 2022/12/1
     */
    private static void addRequestLog(String method, String url, String body, String formParam) {
        log.info("===========================request begin================================================");
        log.info("URI          : {}", url);
        log.info("Method       : {}", method);
        if (StringUtils.isNotBlank(body)) {
            log.info("Request body : {}", body);
        }
        if (StringUtils.isNotBlank(formParam)) {
            log.info("Request param: {}", formParam);
        }
        log.info("---------------------------request end--------------------------------------------------");
    }

    /**
     * 请求成功响应日志
     *
     * @param httpCode  状态码
     * @param result    请求结果
     * @param startTime 请求前计时
     * @author: yh
     * @date: 2022/12/1
     */
    private static void addResponseLog(int httpCode, String result, long startTime) {
        long endTime = System.currentTimeMillis();
        log.info("Status       : {}", httpCode);
        log.info("Response     : {}", result);
        log.info("Time         : {} ms", endTime - startTime);
        log.info("===========================response end================================================");
    }

    /**
     * 状态码不为 200 抛出异常
     *
     * @param httpCode 响应状态码
     * @param result   响应结果
     * @return 响应结果
     * @author: yh
     * @date: 2022/12/1
     */
    private static String handleHttpResponse(int httpCode, String result) throws HttpStatusException {
        if (httpCode == HttpStatus.OK.value()) {
            return result;
        }
        HttpStatus httpStatus = HttpStatus.valueOf(httpCode);
        throw new HttpStatusException(httpStatus);
    }

    /**
     * 打印 异常信息
     *
     * @param ex  异常
     * @param url 请求的路径
     * @author: yh
     * @date: 2022/12/1
     */
    private static void handleHttpThrowable(Exception ex, String url) {
        if (ex instanceof HttpStatusException) {
//            throw (HttpStatusException) ex;
            log.error("OkHttp exception: " + ex.getMessage());
        }
        log.error("OkHttp error url: " + url, ex);
        if (ex instanceof SocketTimeoutException) {
            throw new RuntimeException("request time out of OkHttp when do url:" + url);
        }
        throw new RuntimeException(ex);
    }

}



